package ca.qc.bdeb.jeuxdesboites.model;

import ca.qc.bdeb.jeuxdesboites.commun.Coup;
import ca.qc.bdeb.utilitaires.Direction;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

/**
 *
 * @author Eric Wenaas <eric.wenaas@bdeb.qc.ca>
 */
class GroupesVulnerable implements Iterable<List<Point>> {
    
    private List<List<Point>> groupes;

    public GroupesVulnerable(TableauJeu tableau) {
        construireGroupesVulnerables(tableau);
    }
    
    private void construireGroupesVulnerables(TableauJeu tableau) {
        groupes = new ArrayList<>();
        boolean marque[][] = new boolean[tableau.getLargeur()][tableau.getHauteur()];
        Point p = obtenirProchainNonMarque(tableau, marque);
        while (p != null) {
            List<Coup> coupsSimules = new ArrayList<>();
            LinkedList<Point> nouvelleListe = new LinkedList<>();
            trouverGroupeVulnerable(nouvelleListe, coupsSimules,
                                    marque, tableau, new Point(p.x, p.y));
            if (! nouvelleListe.isEmpty()) {
                groupes.add(nouvelleListe);
            }
            p = obtenirProchainNonMarque(tableau, marque);
            for (Coup unCoup : coupsSimules) {
                tableau.dejouerCoup(unCoup);
            }
        }
        
        ListIterator<List<Point>> iter = groupes.listIterator();
        while (iter.hasNext()) {
            List<Point> nouvelleSousListe = extraireSousGroupe(tableau, iter.next());
            if (nouvelleSousListe != null) {
                iter.add(nouvelleSousListe);
            }
        }
    }

    private List<Point> extraireSousGroupe(TableauJeu tableau, List<Point> groupe) {
        List<Point> sousGroupe = null;
        boolean trouve = false;
        Iterator<Point> iterPoints = groupe.iterator();
        while (iterPoints.hasNext() && ! trouve) {
            Point pointActuel = iterPoints.next();
             sousGroupe = creerGroupe(tableau, pointActuel);
             if (sousGroupe.size() == groupe.size()) {
                 sousGroupe = null;
             } else {
                 trouve = true;
             }
        }
        return sousGroupe;
    }

    private List<Point> creerGroupe(TableauJeu tableau, Point p) {
        List<Point> groupe = new LinkedList<>();
        List<Coup> coupsJoues = new LinkedList<>();
        Boite depart = tableau.getBoite(p.x, p.y);
        ArrayList<Direction> directionsOuvertes = depart.obtenirOuvertures();
        groupe.add(p);

        for (Direction ouverture : directionsOuvertes) {
            Coup leCoup = new Coup(p.x, p.y, ouverture);
            tableau.jouerCoup(null, leCoup);
            coupsJoues.add(leCoup);
        }

        for (Direction ouverture : directionsOuvertes) {
            Point nouveauPoint = tableau.obtenirPointDirection(p, ouverture);
            fermerRecursivement(groupe, coupsJoues, tableau, nouveauPoint);
        }

        for (Coup unCoup : coupsJoues) {
            tableau.dejouerCoup(unCoup);
        }
        return groupe;
    }
    
    private void fermerRecursivement(List<Point> sousGroupe,
                                     List<Coup> coupsJoues,
                                     TableauJeu tableau, 
                                     Point depart) {
        if (depart != null) {

            Boite boite = tableau.getBoite(depart.x, depart.y);
            if (boite.getNombreDirectionsFermes() == 4) {
                // TODO: Patch. Ce cas est nécessaire lorsqu'on ferme deux boîtes
                //       en même temps. Toutefois, lorsqu'on passe deux fois sur
                //       la même boîte alors un point était ajouté deux fois.
                //       Il faut donc vérifier que le point n'est pas déjà là.
                if (! sousGroupe.contains(depart)) {
                    sousGroupe.add(depart);
                }
            } else if (boite.getNombreDirectionsFermes() == 3) {
                Direction ouverture = boite.obtenirOuvertures().get(0);
                Coup leCoup = new Coup(depart.x, depart.y, ouverture);
                tableau.jouerCoup(null, leCoup);
                sousGroupe.add(depart);
                coupsJoues.add(leCoup);
                Point destination = tableau.obtenirPointDirection(depart, ouverture);
                fermerRecursivement(sousGroupe, coupsJoues, tableau,
                                    destination);
            }
        }
    }
    
    
    
    // TODO: L'utilisation d'une liste de points dans lequel on retirerait les points
    // serait probablement plus performant car ici on boucle à partir du début à
    // chaque appel.
    private Point obtenirProchainNonMarque(TableauJeu tableau, boolean marque[][]) {
	int largeur = tableau.getLargeur();
	int hauteur = tableau.getHauteur();
	Point reponse = null;
	for (int x = 0; x < largeur && reponse == null; x++) {
	    for (int y = 0; y < hauteur && reponse == null; y++) {
		if (! marque[x][y]) {
		    reponse = new Point(x, y);
		}
	    }
	}
	return reponse;
    }

    private void trouverGroupeVulnerable(List<Point> list, List<Coup> coups,
                                         boolean marque[][],
                                         TableauJeu tableau, 
                                         Point p) {
        if (p != null) {
            marque[p.x][p.y] = true;
            int nbDirectionsFermees = tableau.getBoite(p.x, p.y).getNombreDirectionsFermes();
            if (nbDirectionsFermees == 2) {
                list.add(p);
                Boite laBoite = tableau.getBoite(p.x, p.y);
                for (Direction d : Direction.values()) {
                    if (!laBoite.isFermeeDirection(d)) {
                        Coup coupJoue = new Coup(p.x, p.y, d);
                        tableau.jouerCoup(null, coupJoue);
                        coups.add(coupJoue);
                        Point nouveauPoint = tableau.obtenirPointDirection(p, d);
                        fermerRecursivement(list, coups, marque, tableau, nouveauPoint);
                    }
                }
            }
        }
    }
    
    private void fermerRecursivement(List<Point> list, 
                                     List<Coup> coups,
                                     boolean marque[][],
                                     TableauJeu tableau, Point p) {
        if (p != null) {
            int nbDirectionsFermees = tableau.getBoite(p.x, p.y).getNombreDirectionsFermes();
            if (nbDirectionsFermees == 3) {
                marque[p.x][p.y] = true;
                list.add(p);
                Boite laBoite = tableau.getBoite(p.x, p.y);
                for (Direction d : Direction.values()) {
                    if (!laBoite.isFermeeDirection(d)) {
                        Coup coupJoue = new Coup(p.x, p.y, d);
                        tableau.jouerCoup(null, coupJoue);
                        coups.add(coupJoue);
                        Point nouveauPoint = tableau.obtenirPointDirection(p, d);
                        fermerRecursivement(list, coups, marque, tableau, nouveauPoint);
                    }
                }
            }
        }
    }

    List<Point> obtenirPlusPetit() {
        int indiceReponse = 0;
        int nombreGroupes = groupes.size();
        int tailleGroupe = 0x7fffffff;
        for (int i=0; i<nombreGroupes; i++) {
            int tailleListe = groupes.get(i).size();
            if (tailleListe < tailleGroupe) {
                indiceReponse = i;
                tailleGroupe = tailleListe;
            }
        }
        return groupes.get(indiceReponse);
    }
    
    int size() {
        return groupes.size();
    }
    
    boolean isEmpty() {
        return groupes.isEmpty();
    }
    
    void remove(List<Point> groupe) {
        groupes.remove(groupe);
    }
    
    

    @Override
    public Iterator<List<Point>> iterator() {
        return groupes.iterator();
    }
}
